//
//  iFlyUncaughtExceptionHandler.m
//  IFlyUncaughtExceptions
//
//  Created by iflytek.com on 12-11-2.
//  Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
//

#import "iFlyUncaughtExceptionHandler.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>

NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";

volatile int32_t UncaughtExceptionCount = 0;
int32_t UncaughtExceptionMaximum = 10;

const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;


extern void install_uncaught_exception_handler();

extern void signal_handler(int signal);

extern void handle_exception(NSException *exception);

extern void uninstall_uncaught_exception_handler();

@implementation iFlyUncaughtExceptionHandler

#pragma mark -
#pragma mark 对外提供的接口

- (void)dealloc
{
    [_saveDefaultPath release];
    _saveDefaultPath = nil;
    [super dealloc];
}

// 该对象不允许释放,如果释放则产生崩溃
- (oneway void)release
{
}

- (id)init
{
    self = [super init];
    if(self)
    {
        NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        
//        _saveDefaultPath = [[NSString alloc] initWithFormat:@"%@/iflyinput_crash.log", docPath];
        _saveDefaultPath = [docPath retain];
    }
    return self;
}

// 崩溃信息默认存储路径
- (void)setSaveCriticalDataDefaultFilePath:(NSString *)defaultPath
{
    if(_saveDefaultPath != defaultPath)
    {
        [_saveDefaultPath release];
        _saveDefaultPath = [defaultPath retain];
    }
}

// 设置应用中处理的最大异常数
- (void)setUncaughtExceptionMaxNum:(int)maxNum
{
    UncaughtExceptionMaximum = maxNum;
}

// 发送邮件，此接口有待完善
- (BOOL)sendMessage:(NSString *)title content:(NSString *)content
{
    return YES;
}

// 注册异常处理器
- (void)installUncaughtExceptionHandler
{
    install_uncaught_exception_handler();
}

// 卸载异常处理器,此函数调用后，将不再处理异常信息
- (void)unInstallUncaughtExceptionHandler
{
    uninstall_uncaught_exception_handler();
}

#pragma mark -
#pragma mark 内部调用接口
+ (NSArray *)backtrace
{
    void* callstack[128];
    int frames = backtrace(callstack, 128);
    char **strs = backtrace_symbols(callstack, frames);
    
    int i;
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (
         i = UncaughtExceptionHandlerSkipAddressCount;
         i < UncaughtExceptionHandlerSkipAddressCount +
         UncaughtExceptionHandlerReportAddressCount;
         i++)
    {
	 	[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);
    
    return backtrace;
}

- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
	if (anIndex == 0)
	{
		_dismissed = YES;
	}
}

// 保存当前用户崩溃信息及用户数据
- (void)validateAndSaveCriticalApplicationData:(NSException *)exception
{
    NSMutableDictionary *dataDictionary = [[NSMutableDictionary alloc] initWithCapacity:0];
    
    NSMutableDictionary *entityDictionary = [[NSMutableDictionary alloc] initWithCapacity:0];
        
    UIDevice *device = [UIDevice currentDevice];
    
    // 获取相关用户数据
    NSString *processName = [[NSProcessInfo processInfo] processName];
    NSString *deviceName = [device name];
    NSString *systemVersion = [device systemVersion];
    NSString *systemName = [device systemName];
    NSString *localizedModel = [device localizedModel];
    NSString *model = [device model];
    int deviceOrientation = [device orientation];
    NSString *exceptionName = [exception name];
    NSDictionary *exceptionDict = [exception userInfo];
    
    exceptionName = (exceptionName == nil) ? @"no exception" : exceptionName;
    exceptionDict = (exceptionDict == nil) ? [NSDictionary dictionary] : exceptionDict;
    
    // 获取日期
    NSDate  *date = [NSDate date];
    NSDateFormatter* formatter = [[[NSDateFormatter alloc] init] autorelease];
    [formatter setDateFormat:@"yyyy-MM-dd_HH:mm:ss:SSS"];
    NSString* dateStr = [formatter stringFromDate:date];
    
    [entityDictionary setValue:processName forKey:@"processName"];
    [entityDictionary setValue:deviceName forKey:@"deviceName"];
    [entityDictionary setValue:systemVersion forKey:@"systemVersion"];
    [entityDictionary setValue:systemName forKey:@"systemName"];
    [entityDictionary setValue:localizedModel forKey:@"localizedModel"];
    [entityDictionary setValue:model forKey:@"model"];
    [entityDictionary setValue:[NSNumber numberWithInt:deviceOrientation] forKey:@"deviceOrientation"];
    [entityDictionary setValue:exceptionName forKey:@"exceptionName"];
    [entityDictionary setValue:exceptionDict forKey:@"exceptionDict"];
    
    [entityDictionary setValue:dateStr forKey:@"dateStr"];
    
    // 保存用户到字典中
    [dataDictionary setValue:entityDictionary forKey:dateStr];
    
    //crash_log_进程名_日期
    NSString *saveFileName = [_saveDefaultPath stringByAppendingFormat:@"/crash_log_%@_%@", processName, dateStr];
    
    NSLog(@"=========================saveFileName = %@", saveFileName);
    
    // 保存到文件中
    BOOL isSuccess = [dataDictionary writeToFile:saveFileName atomically:YES];
    
    if(!isSuccess)
    {
        NSLog(@"validateAndSaveCriticalApplicationData save fail");
    }
    
    [entityDictionary release];
    [dataDictionary release];
}

- (void)handleException:(NSException *)exception
{
	[self validateAndSaveCriticalApplicationData:exception];
	
	UIAlertView *alert =
    [[[UIAlertView alloc]
      initWithTitle:NSLocalizedString(@"Unhandled exception", nil)
      message:[NSString stringWithFormat:NSLocalizedString(
                                                           @"You can try to continue but the application may be unstable.\n\n"
                                                           @"Debug details follow:\n%@\n%@", nil),
               [exception reason],
               [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]
      delegate:self
      cancelButtonTitle:NSLocalizedString(@"Quit", nil)
      otherButtonTitles:NSLocalizedString(@"Continue", nil), nil]
     autorelease];
	[alert show];
	
	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
	CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
    
	while (!_dismissed)
	{
		for (NSString *mode in (NSArray *)allModes)
		{
			CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
		}
	}
	
	CFRelease(allModes);
    
    [self unInstallUncaughtExceptionHandler];
	
	if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName])
	{
		kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
	}
	else
	{
		[exception raise];
	}
}

@end

void handle_exception(NSException *exception)
{
    NSLog(@"====handle_exception step1");
	int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
	if (exceptionCount > UncaughtExceptionMaximum)
	{
		return;
	}
	NSLog(@"====handle_exception step2");
	NSArray *callStack = [iFlyUncaughtExceptionHandler backtrace];
	NSMutableDictionary *userInfo =
    [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
	[userInfo
     setObject:callStack
     forKey:UncaughtExceptionHandlerAddressesKey];
    
	NSLog(@"====handle_exception step3");
	[[[[iFlyUncaughtExceptionHandler alloc] init] autorelease]
     performSelectorOnMainThread:@selector(handleException:)
     withObject:
     [NSException
      exceptionWithName:[exception name]
      reason:[exception reason]
      userInfo:userInfo]
     waitUntilDone:YES];
    
    NSLog(@"====handle_exception step4");
}

void signal_handler(int signal)
{
    NSLog(@"==signal_handler===step1");
	int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
	if (exceptionCount > UncaughtExceptionMaximum)
	{
		return;
	}
    
	NSLog(@"===signal_handler==step2");
	NSMutableDictionary *userInfo =
    [NSMutableDictionary
     dictionaryWithObject:[NSNumber numberWithInt:signal]
     forKey:UncaughtExceptionHandlerSignalKey];
    
    NSLog(@"===signal_handler==step3");
	NSArray *callStack = [iFlyUncaughtExceptionHandler backtrace];
	[userInfo
     setObject:callStack
     forKey:UncaughtExceptionHandlerAddressesKey];
	
    NSLog(@"===signal_handler==step4");
	[[[[iFlyUncaughtExceptionHandler alloc] init] autorelease]
     performSelectorOnMainThread:@selector(handleException:)
     withObject:
     [NSException
      exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
      reason:
      [NSString stringWithFormat:
       NSLocalizedString(@"Signal %d was raised.", nil),
       signal]
      userInfo:
      [NSDictionary
       dictionaryWithObject:[NSNumber numberWithInt:signal]
       forKey:UncaughtExceptionHandlerSignalKey]]
     waitUntilDone:YES];
    NSLog(@"===signal_handler==step5");
}

void install_uncaught_exception_handler()
{
	NSSetUncaughtExceptionHandler(&handle_exception);
	signal(SIGABRT, signal_handler);
	signal(SIGILL, signal_handler);
	signal(SIGSEGV, signal_handler);
	signal(SIGFPE, signal_handler);
	signal(SIGBUS, signal_handler);
	signal(SIGPIPE, signal_handler);
}

void uninstall_uncaught_exception_handler()
{
    NSSetUncaughtExceptionHandler(NULL);
	signal(SIGABRT, SIG_DFL);
	signal(SIGILL, SIG_DFL);
	signal(SIGSEGV, SIG_DFL);
	signal(SIGFPE, SIG_DFL);
	signal(SIGBUS, SIG_DFL);
	signal(SIGPIPE, SIG_DFL);
}
